package com.smallx.xpath.parser;

import com.smallx.xpath.SimpleStaticContext;
import java.net.*;
import java.util.*;

import com.smallx.xpath.StaticContext;
import org.infoset.xml.Item;
import org.infoset.xml.Name;
import org.infoset.xml.NamespaceScope;
import org.infoset.xml.XMLException;

public class XPathParser extends XPathLexer {
  private static Map<String,Expression.Axis> axisTable = new HashMap<String,Expression.Axis>();
  static {
    axisTable.put("child", Expression.Axis.Child);
    axisTable.put("parent", Expression.Axis.Parent);
    axisTable.put("self", Expression.Axis.Self);
    axisTable.put("attribute", Expression.Axis.Attribute);
    axisTable.put("descendant-or-self", Expression.Axis.DescendantOrSelf);
    axisTable.put("descendant", Expression.Axis.Descendant);
    axisTable.put("ancestor-or-self", Expression.Axis.AncestorOrSelf);
    axisTable.put("ancestor", Expression.Axis.Ancestor);
    axisTable.put("following-sibling", Expression.Axis.FollowingSibling);
    axisTable.put("preceding-sibling",Expression.Axis.PrecedingSibling);
    axisTable.put("following", Expression.Axis.Following);
    axisTable.put("preceding", Expression.Axis.Preceding);
    axisTable.put("namespace", Expression.Axis.Namespace);
  }
  /*
  private boolean usesCurrentFunction = false;

  static final private Map axisTable = new HashMap();
  static final private AxisExpr childAxis = new ChildAxisExpr();
  static final private AxisExpr parentAxis = new ParentAxisExpr();
  static final private AxisExpr selfAxis = new SelfAxisExpr();
  static final private AxisExpr attributeAxis = new AttributeAxisExpr();
  static final private AxisExpr descendantOrSelfAxis = new DescendantOrSelfAxisExpr();
  
  static final private Map extensions = new HashMap();
  static final private Map functionTable = new HashMap();
  static final private Map extensionFunctionTable = new HashMap();
  static final private Map dateAndTimesFunctionTable = new HashMap();
  static final private Map xpathFunctionsTable = new HashMap();

  static final private Relation equalsRelation = new EqualsRelation();
  static final private Relation notEqualsRelation = new NotEqualsRelation();
  static final private Relation greaterThanEqualsRelation = new GreaterThanEqualsRelation();
  static final private Relation greaterThanRelation = new GreaterThanRelation();
  static final private Function currentFunction = new CurrentFunction();
 
  static {
    axisTable.put("child", childAxis);
    axisTable.put("parent", parentAxis);
    axisTable.put("self", selfAxis);
    axisTable.put("attribute", attributeAxis);
    axisTable.put("descendant-or-self", descendantOrSelfAxis);
    axisTable.put("descendant", new DescendantAxisExpr());
    axisTable.put("ancestor-or-self", new AncestorOrSelfAxisExpr());
    axisTable.put("ancestor", new AncestorAxisExpr());
    axisTable.put("following-sibling", new FollowingSiblingAxisExpr());
    axisTable.put("preceding-sibling", new PrecedingSiblingAxisExpr());
    axisTable.put("following", new FollowingAxisExpr());
    axisTable.put("preceding", new PrecedingAxisExpr());
    axisTable.put("namespace", new NamespaceAxisExpr());

    functionTable.put("boolean", new BooleanFunction());
    functionTable.put("ceiling", new CeilingFunction());
    functionTable.put("concat", new ConcatFunction());
    functionTable.put("contains", new ContainsFunction());
    functionTable.put("count", new CountFunction());
    functionTable.put("document", new DocumentFunction());
    functionTable.put("false", new FalseFunction());
    functionTable.put("floor", new FloorFunction());
    functionTable.put("format-number", new FormatNumberFunction());
    functionTable.put("function-available",
		      new FunctionAvailableFunction());
    // functionTable.put("element-available",
    //		      new ElementAvailableFunction());
    functionTable.put("generate-id", new GenerateIdFunction());
    functionTable.put("id", new IdFunction());
    functionTable.put("key", new KeyFunction());
    functionTable.put("lang", new LangFunction());
    functionTable.put("last", new LastFunction());
    functionTable.put("local-name", new LocalNameFunction());
    functionTable.put("namespace-uri", new NamespaceUriFunction());
    functionTable.put("normalize-space", new NormalizeSpaceFunction());
    functionTable.put("not", new NotFunction());
    functionTable.put("number", new NumberFunction());
    functionTable.put("position", new PositionFunction());
    functionTable.put("name", new NameFunction());
    functionTable.put("round", new RoundFunction());
    functionTable.put("starts-with", new StartsWithFunction());
    functionTable.put("string", new StringFunction());
    functionTable.put("string-length", new StringLengthFunction());
    functionTable.put("substring", new SubstringFunction());
    functionTable.put("substring-after", new SubstringAfterFunction());
    functionTable.put("substring-before", new SubstringBeforeFunction());
    functionTable.put("sum", new SumFunction());
    functionTable.put("system-property", new SystemPropertyFunction());
    functionTable.put("translate", new TranslateFunction());
    functionTable.put("true", new TrueFunction());
    functionTable.put("unparsed-entity-uri", new UnparsedEntityURIFunction());
    extensions.put(XT_NAMESPACE,extensionFunctionTable);
    extensions.put(EXSLT.DATE_AND_TIMES_NAMESPACE,dateAndTimesFunctionTable);
    extensions.put(com.smallx.xpath.document.functions.Extension.NAMESPACE,xpathFunctionsTable);
    extensionFunctionTable.put("node-set", new NodeSetFunction());
    extensionFunctionTable.put("intersection", new IntersectionFunction());
    extensionFunctionTable.put("difference", new DifferenceFunction());
    extensionFunctionTable.put("base-uri", new BaseURIFunction());
    extensionFunctionTable.put("resolve", new RelativeURIFunction());
    extensionFunctionTable.put("uuid", new UUIDFunction());
    extensionFunctionTable.put("epoch2date-time", new Epoch2DateTimeFunction());
    extensionFunctionTable.put("range", new RangeFunction());
    EXSLT.loadDateAndTimes(dateAndTimesFunctionTable);
    com.smallx.xpath.document.functions.Extension.load(xpathFunctionsTable);
  }
 
  public static NodeSetExpr parseNodeSetExpr(Item node, String expr, Collection locals) 
     throws XMLException 
  {
      return parseConvertibleExpr(node, expr, locals).makeNodeSetExpr();
  }

  public static NodeSetExpr parseNodeSetExpr(StaticContext context,String expr) 
     throws XMLException 
  {
      return parseConvertibleExpr(context, expr).makeNodeSetExpr();
  }

  public static NodeSetExpr parseNodeSetExpr(Item node, NamespaceScope resolver, String expr, Collection locals) 
     throws XMLException 
  {
      return parseConvertibleExpr(node, resolver, expr, locals).makeNodeSetExpr();
  }

  public static StringExpr parseStringExpr(Item node, String expr, Collection locals) 
     throws XMLException 
  {
      return parseConvertibleExpr(node, expr, locals).makeStringExpr();
  }

  public static NumberExpr parseNumberExpr(Item node, String expr, Collection locals) 
     throws XMLException 
  {
      return parseConvertibleExpr(node, expr, locals).makeNumberExpr();
  }

  public static BooleanExpr parseBooleanExpr(Item node, String expr, Collection locals) 
     throws XMLException 
  {
      return parseConvertibleExpr(node, expr, locals).makeBooleanExpr();
  }

  public static VariantExpr parseVariantExpr(Item node, String expr, Collection locals) 
     throws XMLException 
  {
      return parseConvertibleExpr(node, expr, locals).makeVariantExpr();
  }

  public static ConvertibleExpr parseConvertibleExpr(Item node, String expr, Collection locals)
    throws XMLException 
  {
     return new XPathParser(expr, new NodeStaticContext(node,locals)).parseExpr();
  }

  public static ConvertibleExpr parseConvertibleExpr(StaticContext context, String expr)
    throws XMLException 
  {
     return new XPathParser(expr, context).parseExpr();
  }

  public static ConvertibleExpr parseConvertibleExpr(Item node, NamespaceScope resolver,String expr, Collection locals)
    throws XMLException 
  {
     return new XPathParser(expr, new NodeStaticContext(node, resolver ,locals)).parseExpr();
  }
   */

  private StaticContext scontext;
  
  public XPathParser() {
     this(new SimpleStaticContext());
  }
  public XPathParser(StaticContext scontext) {
    this.scontext = scontext;
  }
  
  public Expression parseExpr() 
     throws XMLException 
  {
    next();
    Expression expr = parseOrExpr();
    if (currentToken != TOK_EOF) {
       throw new XMLException("unexpected token: "+currentTokenValue);
    }
    return expr;
  }

  public Expression parseOrExpr() 
     throws XMLException 
  {
    Expression expr = parseAndExpr();
    ListOfExpressions orExpr = null;
    while (currentToken == TOK_OR) {
      next();
      if (orExpr==null) {
         orExpr = new ListOfExpressions(Expression.Operator.Or,expr);
      }
      expr = parseAndExpr();
      orExpr.add(expr);
    }
    return orExpr==null ? expr : orExpr;
  }

  public Expression parseAndExpr() 
     throws XMLException 
  {
    Expression expr = parseEqualityExpr();
    ListOfExpressions andExpr = null;
    while (currentToken == TOK_AND) {
      next();
      if (andExpr==null) {
         andExpr = new ListOfExpressions(Expression.Operator.And,expr);
      }
      expr = parseEqualityExpr();
      andExpr.add(expr);
    }
    return andExpr==null ? expr : andExpr;
  }

  public Expression parseEqualityExpr() 
     throws XMLException 
  {
     Expression expr = parseRelationalExpr();
     loop:
        for (;;) {
           switch (currentToken) {
               case TOK_EQUALS:
                 next();
                 expr = new PairExpression(Expression.Operator.Equals,expr,parseRelationalExpr());
                 break;
              case TOK_NOT_EQUALS:
                 next();
                 expr = new PairExpression(Expression.Operator.NotEquals,expr,parseRelationalExpr());
                 break;
              default:
                 break loop;
           }
        }
     return expr;
  }

  public Expression parseRelationalExpr() 
     throws XMLException 
  { 
     Expression expr = parseAdditiveExpr();
     loop:
        for (;;) {
           switch (currentToken) {
              case TOK_GT:
                 next();
                 expr = new PairExpression(Expression.Operator.GreaterThan,expr,parseAdditiveExpr());
                 break;
              case TOK_GTE:
                 next();
                 expr = new PairExpression(Expression.Operator.GreaterThanEquals,expr,parseAdditiveExpr());
                 break;
              case TOK_LT:
                 next();
                 expr = new PairExpression(Expression.Operator.GreaterThan,parseAdditiveExpr(),expr);
                 break;
              case TOK_LTE:
                 next();
                 expr = new PairExpression(Expression.Operator.GreaterThanEquals,parseAdditiveExpr(),expr);
                 break;
              default:
                 break loop;
           }
       }
    return expr;
  }
  
  public Expression parseAdditiveExpr() 
     throws XMLException 
  {
     Expression expr = parseMultiplicativeExpr();
     loop:
        for (;;) {
           switch (currentToken) {
              case TOK_PLUS:
                 next();
                 expr = new PairExpression(Expression.Operator.Add,expr,parseMultiplicativeExpr());
                 break;
              case TOK_MINUS:
                 next();
                 expr = new PairExpression(Expression.Operator.Subtract,expr,parseMultiplicativeExpr());
                 break;
              default:
                 break loop;
           }
        }
     return expr;
  }

  public Expression parseMultiplicativeExpr() 
     throws XMLException 
  {
     Expression expr = parseUnaryExpr();
     loop:
        for (;;) {
           switch (currentToken) {
              case TOK_DIV:
                 next();
                 expr = new PairExpression(Expression.Operator.Divide,expr,parseUnaryExpr());
                 break;
              case TOK_MOD:
                 next();
                 expr = new PairExpression(Expression.Operator.Modulo,expr,parseUnaryExpr());
                 break;
              case TOK_MULTIPLY:
                 next();
                 expr = new PairExpression(Expression.Operator.Multiply,expr,parseUnaryExpr());
                 break;
              default:
                 break loop;
           }
        }
     return expr;
  }

  public Expression parseUnaryExpr() 
     throws XMLException 
  {
     if (currentToken == TOK_MINUS) {
        next();
        return new SingletonExpression(Expression.Operator.Negate,parseUnaryExpr());
     } 
     return parseUnionExpr();
  }

  public Expression parseUnionExpr() 
     throws XMLException 
  {
     Expression expr = parsePathExpr();
     ListOfExpressions unionExpr = null;
     while (currentToken == TOK_VBAR) {
       next();
       if (unionExpr==null) {
          unionExpr = new ListOfExpressions(Expression.Operator.Union,expr);
       }
       expr = parsePathExpr();
       unionExpr.add(expr);
     }
     return unionExpr==null ? expr : unionExpr;
  }

  public Expression parsePathExpr() 
     throws XMLException 
  {
     if (tokenStartsStep()) {
        return parseRelativeLocationPath();
     }
     if (currentToken == TOK_SLASH) {
        next();
        if (tokenStartsStep()) {
           return new SingletonExpression(Expression.Operator.Root,parseRelativeLocationPath());
        }
        return new VoidExpression(Expression.Operator.Root);
     }
     if (currentToken == TOK_SLASH_SLASH) {
        ListOfExpressions path = new ListOfExpressions(Expression.Operator.Path);
        return new SingletonExpression(
                      Expression.Operator.Root,
                      parseRelativeLocationPath(path));
     } 
     Expression expr = parsePrimaryExpr();
     while (currentToken == TOK_LSQB) {
        next();
        expr = new PairExpression(Expression.Operator.Filter,expr,parseOrExpr());
        expectRsqb();
     }
     if (currentToken == TOK_SLASH) {
        ListOfExpressions path = new ListOfExpressions(Expression.Operator.Path);
        path.add(expr);
        return parseRelativeLocationPath(path);
     } else if (currentToken == TOK_SLASH_SLASH) {
        ListOfExpressions path = new ListOfExpressions(Expression.Operator.Path);
        path.add(expr);
        return parseRelativeLocationPath(path);
     } else {
        return expr;
     }
  }

  public Expression parseRelativeLocationPath() 
     throws XMLException 
  {
     ListOfExpressions path = new ListOfExpressions(Expression.Operator.Path);
     path.add(parseStep());
     return parseRelativeLocationPath(path);
  }
  
  protected Expression parseRelativeLocationPath(ListOfExpressions path)
     throws XMLException
  {
     while (currentToken==TOK_SLASH || currentToken==TOK_SLASH_SLASH) {
        if (currentToken==TOK_SLASH_SLASH) {
           path.add(new VoidExpression(Expression.Operator.DescendantOrSelf));
        }
        next();
        
        path.add(parseStep());
     }
     return path;
  }

  public Expression parseStep() 
     throws XMLException 
  {
     switch (currentToken) {
        case TOK_AXIS:
        {
           Expression.Axis axis = axisTable.get(currentTokenValue);
           if (axis == null) {
              throw new XMLException("No axis named "+currentTokenValue);
           }
           Item.ItemType typeTestCode;
           if (currentTokenValue.equals("attribute")) {
              typeTestCode=Item.ItemType.AttributeItem;
           } else if (currentTokenValue.equals("namespace")) {
              typeTestCode=Item.ItemType.NamespaceItem;
           } else {
              typeTestCode=Item.ItemType.ElementItem;
           }
           next();
           return parsePredicates(axis, parseNodeTest(typeTestCode));
        }
        case TOK_DOT:
           next();
           return new VoidExpression(Expression.Operator.Self);
        case TOK_DOT_DOT:
           next();
           return new VoidExpression(Expression.Operator.Parent);
        case TOK_AT:
           next();
           return parsePredicates(Expression.Axis.Attribute, parseNodeTest(Item.ItemType.AttributeItem));
        default:
           return parsePredicates(Expression.Axis.Child, parseNodeTest(Item.ItemType.ElementItem));
     }
  }

  public Expression parsePredicates(Expression.Axis axis, NodeTestExpression nodeTest) throws XMLException {
     Expression expr = new StepExpression(axis, nodeTest);
     while (currentToken == TOK_LSQB) {
        next();
        expr = new PairExpression(Expression.Operator.Filter,expr, parseOrExpr());
        expectRsqb();
     }
     return expr;
  }

  /* return null if the test is vacuous */

  public NodeTestExpression parseNodeTest(Item.ItemType typeCode) throws XMLException {
     NodeTestExpression nodeTest = null;
     switch (currentToken) {
        case TOK_QNAME:
           switch (typeCode) {
              case NamespaceItem:
              {
                 Name nm = expandName();
                 if (nm.getNamespaceName()!=Name.NO_NAMESPACE) {
                    throw new XMLException("No namespace may be given on the namespace axis.");
                 }
                 nodeTest = new NodeTestExpression(typeCode,nm);
              }
              default:
                 nodeTest = new NodeTestExpression(typeCode,expandName());
           }
           break;
        case TOK_STAR:
           nodeTest = new NodeTestExpression(typeCode,null);
           break;
        case TOK_NAME_COLON_STAR:
           switch (typeCode) {
              case AttributeItem:
              case ElementItem:
                 nodeTest = new NamespaceTestExpression(typeCode,expandPrefix());
                 break;
              default:
                 throw new XMLException("Cannot handle a namespace test on a axis that isn't an attribute or child.");
           }
           break;
        case TOK_PROCESSING_INSTRUCTION_LPAR:
           next();
           if (currentToken == TOK_LITERAL) {
              nodeTest = new NodeTestExpression(Item.ItemType.ProcessingInstructionItem,expandName());
              next();
           } else {
              nodeTest = new NodeTestExpression(Item.ItemType.ProcessingInstructionItem,null);
           }
           expectRpar();
           return nodeTest;
        case TOK_COMMENT_LPAR:
           next();
           expectRpar();
           nodeTest = new NodeTestExpression(Item.ItemType.CommentItem,null);
           break;
        case TOK_TEXT_LPAR:
           next();
           expectRpar();
           nodeTest = new NodeTestExpression(Item.ItemType.CharactersItem,null);
           break;
        case TOK_NODE_LPAR:
           next();
           expectRpar();
           nodeTest = new NodeTestExpression(null,null);
           break;
        default:
           throw new XMLException("Expected node test");
     }
     next();
     return nodeTest;
  }

  private final void expectRpar() throws XMLException {
    if (currentToken != TOK_RPAR)
      throw new XMLException("expected )");
    next();
  }

  private final void expectRsqb() throws XMLException {
    if (currentToken != TOK_RSQB)
      throw new XMLException("expected ]");
    next();
  }

  public Expression parsePrimaryExpr() throws XMLException {
     Expression expr;
     switch (currentToken) {
        case TOK_VARIABLE_REF:
        {
           Name name = expandName();
           if (scontext.getInScopeVariables().contains(name)) {
              expr = new VariableReference(false,name);
           } else {
              expr = new VariableReference(true,name);
           }
           break;
        }
        case TOK_LPAR:
           next();
           expr = parseOrExpr();
           expectRpar();
           return expr;
        case TOK_LITERAL:
           expr = new LiteralExpression(currentTokenValue);
           break;
        case TOK_NUMBER:
           expr = new NumberExpression(Double.parseDouble(currentTokenValue));
           break;
        case TOK_FUNCTION_LPAR:
        case TOK_CNAME_LPAR:
        {
           expr = new FunctionExpression(Name.create(currentTokenValue));
           next();
           parseArgs((ListOfExpressions)expr);
           return expr;
        }
        default:
           throw new XMLException("Syntax error in primary expression.  Unexpected token ("+currentToken+")");
     }
     next();
     return expr;
  }

  protected void parseArgs(ListOfExpressions function) 
     throws XMLException 
  {
    if (currentToken == TOK_RPAR) {
      next();
      return;
    }
    for (;;) {
      function.add(parseOrExpr());
      if (currentToken != TOK_COMMA) {
  	 break;
      }
      next();
    } 
    expectRpar();
  }

  private boolean tokenStartsNodeTest() {
     switch (currentToken) {
        case TOK_QNAME:
        case TOK_STAR:
        case TOK_NAME_COLON_STAR:
        case TOK_PROCESSING_INSTRUCTION_LPAR:
        case TOK_COMMENT_LPAR:
        case TOK_TEXT_LPAR:
        case TOK_NODE_LPAR:
           return true;
     }
     return false;
  }

  private boolean tokenStartsStep() {
     switch (currentToken) {
        case TOK_AXIS:
        case TOK_DOT:
        case TOK_DOT_DOT:
        case TOK_AT:
           return true;
     }
     return tokenStartsNodeTest();
  }

  private Name expandName() throws XMLException {
     NamespaceScope prefixMap = scontext.getNamespaceScope();
     if (prefixMap != null) {
        try {
           return prefixMap.expandName(currentTokenValue,false);
        } catch (XMLException ex) {
           throw new XMLException(ex.getMessage(),ex);
        }
     } else {
        return null;
     }
  }

  private URI expandPrefix() throws XMLException {
     NamespaceScope prefixMap = scontext.getNamespaceScope();
     if (prefixMap == null) {
        return null;
     }
     URI ns = prefixMap.getNamespace(currentTokenValue);
     if (ns == null) {
        throw new XMLException("Undefined prefix in "+currentTokenValue);
     }
     return ns;
  }

  private static int findExprEnd(String value, int i) {
    int valueLen = value.length();
    char quote = '\0';
    for (; i < valueLen; i++) {
      char c = value.charAt(i);
      switch (c) {
      case '}':
	if (quote == '\0')
	  return i;
	break;
      case '\"':
      case '\'':
	if (quote == c)
	  quote = '\0';
	else if (quote == '\0')
	  quote = c;
        break;
      }
    }
    return -1;
  }

}
